/*
This file is part of IRCaBot.
IRCaBot is IRC logger with features.
Source code: https://notabug.org/acetone/ircabot.
Copyright (C) acetone, 2023.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "currenttime.h"
#include "adminircpanel.h"
#include "ircnetworkclient.h"
#include "config.h"
#include "voicegate.h"

#include <QMutex>
#include <QRegularExpression>

QMap<QString,qint64> AdminIRCPanel::m_identifiedTimestamps;

void AdminIRCPanel::identifiedTimestampUpdate(const QString& networkName)
{
    static QMutex mtx;
    QMutexLocker lock (&mtx);
    m_identifiedTimestamps[networkName] = currentTimestampSecs();
}

bool AdminIRCPanel::parse(const QString& networkName, const QString &input, QStringList& answer, VoicedList& voicedList, bool& voicedListShouldReload, QSharedPointer<Config::Network> config)
{
    voicedListShouldReload = false;
    static QMutex mtx;
    if (not mtx.tryLock())
    {
        answer.push_back("The admin panel is busy. Try later.");
        return true;
    }
    mtx.unlock();
    QMutexLocker lock (&mtx);

    if (not m_identifiedTimestamps.contains(networkName) or
        currentTimestampSecs() - m_identifiedTimestamps[networkName] > Config::ircAdminAuthorizationTimeout())
    {
        return false;
    }

    static const QRegularExpression rgx_help          ("^!help$", QRegularExpression::CaseInsensitiveOption);
    static const QRegularExpression rgx_channel       ("^!channel (add|del) [^\\s]{1,100}$", QRegularExpression::CaseInsensitiveOption);
    static const QRegularExpression rgx_bridge        ("^!bridge (add |del |list).*$", QRegularExpression::CaseInsensitiveOption);
    static const QRegularExpression rgx_voiceGate     ("^!voiceGate set (enabled|disabled)$", QRegularExpression::CaseInsensitiveOption);
    static const QRegularExpression rgx_voiceGateAoD  ("^!voiceGate (add|del) .*$", QRegularExpression::CaseInsensitiveOption);
    static const QRegularExpression rgx_voiceGateCSen ("^!voiceGate set case sensitive (enabled|disabled)$", QRegularExpression::CaseInsensitiveOption);
    static const QRegularExpression rgx_voiceGateON   ("^!voiceGate set only numbers (enabled|disabled)$", QRegularExpression::CaseInsensitiveOption);
    static const QRegularExpression rgx_voiceGateCSiz ("^!voiceGate set captcha cache size (-)?[0-9]*$", QRegularExpression::CaseInsensitiveOption);
    static const QRegularExpression rgx_voiceGateDiff ("^!voiceGate set captcha difficulty (-)?[0-2]*$", QRegularExpression::CaseInsensitiveOption);
    static const QRegularExpression rgx_reloadChannels("^!reload channels$", QRegularExpression::CaseInsensitiveOption);
    static const QRegularExpression rgx_updateCfg     ("^!update config$", QRegularExpression::CaseInsensitiveOption);

    if (input.contains(rgx_help))
    {   // https://modern.ircdocs.horse/formatting.html#bold
        answer = QStringList() <<
                         QString(QString("Add channel to my list or remove one: ")+0x02+                        QString("!channel <add|del> <channel name>")) <<
                         QString(QString("After deleting or adding channels, reload connections: ")+0x02+       QString("!reload channels")) <<
                         QString(QString("Bridges in this network: ")+0x02+                                     QString("!bridge <add|del|list> <nickname>")) <<
                         QString(QString("Voice gate in this network: ")+0x02+                                  QString("!voiceGate set <enabled|disabled>")) <<
                         QString(QString("Voice gate in this network: ")+0x02+                                  QString("!voiceGate <add|del> <nickname>")) <<
                         QString(QString("Global voice gate option: ")+0x02+                                    QString("!voiceGate set case sensitive <enabled|disabled>")) <<
                         QString(QString("Global voice gate option: ")+0x02+                                    QString("!voiceGate set only numbers <enabled|disabled>")) <<
                         QString(QString("Global voice gate option: ")+0x02+                                    QString("!voiceGate set captcha cache size <positive number>")) <<
                         QString(QString("Global voice gate option: ")+0x02+                                    QString("!voiceGate set captcha difficulty <0-2>")) <<
                         QString(QString("Overwrite the configuration file with the current data: ")+0x02+      QString("!update config")) <<
                         "END OF HELP";
    }

    else if (input.contains(rgx_channel))
    {
        bool add = input.split(' ').at(1).contains("add", Qt::CaseInsensitive);
        QString channel = input.split(' ').at(2);


        if (add and config->channels.contains(channel, Qt::CaseInsensitive))
        {
            answer.push_back("Channel " + channel + " already exists.");
            return true;
        }
        else if (add)
        {
            config->channels.push_back(channel);
            answer.push_back("Success. Channel " + channel + " added.");
            return true;
        }

        else if (not add and not config->channels.contains(channel, Qt::CaseInsensitive))
        {
            answer.push_back("Channel " + channel + " not exists.");
            return true;
        }
        else if (not add)
        {
            config->channels.removeOne(channel);
            answer.push_back("Success. Channel " + channel + " removed.");
            return true;
        }
    }

    else if (input.contains(rgx_reloadChannels))
    {
        uint changeCounter = 0;
        auto configs = Config::networksAccess();
        for (auto iter = configs.begin(); iter != configs.end(); ++iter)
        {
            changeCounter = (*iter)->instance->reloadChannelList();
        }
        answer.push_back("Success. Reloaded: " + QString::number(changeCounter) + " changes.");
    }

    else if (input.contains(rgx_voiceGate))
    {
        bool enable = input.endsWith("enabled", Qt::CaseInsensitive);
        if (config->voiceGate == enable)
        {
            answer.push_back("Voice gate already " + QString(enable ? "enabled" : "disabled") + ".");
            return true;
        }
        config->voiceGate = enable;
        answer.push_back("Success. Voice gate " + QString(enable ? "enabled" : "disabled") + ".");
    }

    else if (input.contains(rgx_voiceGateCSen))
    {
        bool enable = input.endsWith("enabled", Qt::CaseInsensitive);
        bool oldStatus = Config::voiceGate().captchaCaseSensitive;
        if (enable == oldStatus)
        {
            answer.push_back("Voice gate already case " + QString(enable ? "sensitive" : "insensitive") + ".");
            return true;
        }
        Config::voiceGateAccess().captchaCaseSensitive = enable;
        answer.push_back("Success. Voice gate " + QString(enable ? "sensitive" : "insensitive") + ".");
    }

    else if (input.contains(rgx_voiceGateON))
    {
        bool enable = input.endsWith("enabled", Qt::CaseInsensitive);
        bool oldStatus = Config::voiceGate().captchaOnlyNumbers;
        if (enable == oldStatus)
        {
            answer.push_back("Voice gate already have only numbers mode " + QString(enable ? "enabled" : "disabled") + ".");
            return true;
        }
        Config::voiceGateAccess().captchaOnlyNumbers = enable;
        VoiceGate::dropCache();
        answer.push_back("Success. Voice gate only numbers mode " + QString(enable ? "enabled" : "disabled") + ". Old cache dropped.");
    }

    else if (input.contains(rgx_voiceGateCSiz))
    {
        bool converted = false;
        int number = input.split(' ').last().toInt(&converted);
        if (not converted or number < 0)
        {
            answer.push_back("Invalid number passed.");
            return true;
        }
        auto oldValue = Config::voiceGateAccess().captchaCacheSize;
        if (oldValue == number)
        {
            answer.push_back("Voice gate cache size already is " + QString::number(oldValue) + ".");
            return true;
        }
        Config::voiceGateAccess().captchaCacheSize = number;
        answer.push_back("Success. Voice gate cache size set from " + QString::number(oldValue) + " to " + QString::number(number) + ".");
    }

    else if (input.contains(rgx_voiceGateDiff))
    {
        bool converted = false;
        int number = input.split(' ').last().toInt(&converted);
        if (not converted or number < 0 or number > 2)
        {
            answer.push_back("Invalid number passed.");
            return true;
        }
        int oldStatus = Config::voiceGate().captchaDifficulty;
        if (number == oldStatus)
        {
            answer.push_back("Voice gate difficulty already is " + QString::number(number) + ".");
            return true;
        }
        Config::voiceGateAccess().captchaDifficulty = number;
        VoiceGate::dropCache();
        answer.push_back("Success. Voice gate difficulty set to " + QString::number(number) + ". Old cache dropped.");
    }

    else if (input.contains(rgx_voiceGateAoD))
    {
        bool add = false;
        if (input.startsWith("!voiceGate add ", Qt::CaseInsensitive))
        {
            add = true;
        }
        else if (not input.startsWith("!voiceGate del ", Qt::CaseInsensitive))
        {
            answer.push_back("Command not recognized");
            return true;
        }

        QString nick(input);
        nick.remove(0, 15);
        nick = nick.trimmed();
        if (nick.isEmpty())
        {
            answer.push_back("Failure: no nickname passed.");
            return true;
        }

        if (add)
        {
            voicedList.add(nick);
            answer.push_back("Success. " + nick + " added to voiced list of this network.");
            voicedListShouldReload = true;
        }
        else
        {
            const bool status = voicedList.del(nick);
            answer.push_back(status ? "Success. " + nick + " deleted from voiced list of this network." : "Failure: nickname " + nick + " not exists in voiced database.");
        }
    }

    else if (input.contains(rgx_bridge))
    {
        if (input.startsWith("!bridge list", Qt::CaseInsensitive))
        {
            QString result = "Current bridge nicknames: ";
            if (config->bridgeNicknames.isEmpty())
            {
                result += "<EMPTY>";
            }
            else
            {
                QStringListIterator iter(config->bridgeNicknames);
                while (iter.hasNext())
                {
                    result += iter.next();
                    if (iter.hasNext())
                    {
                        result += ", ";
                    }
                }
            }
            answer.push_back(result);
            return true;
        }

        const bool add = input.startsWith("!bridge add ", Qt::CaseInsensitive);
        QString nick (input);
        nick.remove(0, 12);
        if (nick.isEmpty())
        {
            answer.push_back("Failure: no nickname passed.");
            return true;
        }

        if (add)
        {
            if (config->bridgeNicknames.contains(nick))
            {
                answer.push_back("Failure: " + nick + " already in bridge nicknames list.");
                return true;
            }
            config->bridgeNicknames.push_back(nick);
            answer.push_back("Success: " + nick + " added to bridge nicknames list.");
        }
        else
        {
            if (not config->bridgeNicknames.contains(nick))
            {
                answer.push_back("Failure: " + nick + " not exists in bridge nicknames list.");
                return true;
            }
            config->bridgeNicknames.removeAll(nick);
            answer.push_back("Success: " + nick + " removed from bridge nicknames list.");
        }
    }

    else if (input.contains(rgx_updateCfg))
    {
        QString error;
        const bool status = Config::updateConfigWithCurrentData(&error);
        answer.push_back(status ? "Success. Configuration file updated." : "Failure: " + error + ".");
    }

    else
    {
        answer.push_back("Unknown command. Try \"!help\" :)");
    }

    return true;
}
